/** @file

Copyright (c) 2006-2023, Kunlun BIOS, Kunlun Technology (Beijing) Co., Ltd.. All
Rights Reserved.

You may not reproduce, distribute, publish, display, perform, modify, adapt,
transmit, broadcast, present, recite, release, license or otherwise exploit
any part of this publication in any form, by any means, without the prior
written permission of Kunlun Technology (Beijing) Co., Ltd..

Module Name:

  KlDevicePathOrderLib.c

Abstract:


Revision History:


**/



#include <Library/KlDevicePathOrderLib.h>
#include <Protocol/BlockIo.h>
#include <Protocol/UsbIo.h>
#include <Library/UefiBootServicesTableLib.h>

UINT16  BootDeviceOrder[8] = {
  BDS_EFI_MESSAGE_NVME_BOOT,
  BDS_EFI_MESSAGE_SATA_BOOT,
  BDS_EFI_MESSAGE_SCSI_BOOT,
  BDS_EFI_MEDIA_CDROM_BOOT,
  BDS_EFI_MESSAGE_MAC_BOOT,
  BDS_EFI_MESSAGE_USB_DEVICE_BOOT,
  BDS_EFI_MESSAGE_USB_CDROM_BOOT,
  BDS_EFI_MEDIA_FW_FILE_BOOT
};


/**
  Brief description of IsUsbCdromBootOption.

  @param  DevicePath

**/
BOOLEAN
IsUsbCdromBootOption (
   EFI_DEVICE_PATH_PROTOCOL      *DevicePath
)
{
  EFI_STATUS                        Status;
  UINTN                             Index;
  EFI_USB_IO_PROTOCOL               *UsbIo;
  UINTN                             UsbIoHandleCount;
  EFI_HANDLE                        *UsbIoHandleBuffer;
  EFI_DEVICE_PATH_PROTOCOL          *UsbIoDevicePath;
  EFI_USB_DEVICE_DESCRIPTOR         DevDesc;
  EFI_USB_INTERFACE_DESCRIPTOR      IfDesc;
  UINT8                             DeviceClass;
  UINT8                             DeviceSubClass;


  //
  // Get all UsbIo Handles.
  //
  UsbIoHandleCount = 0;
  UsbIoHandleBuffer = NULL;
  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  &gEfiUsbIoProtocolGuid,
                  NULL,
                  &UsbIoHandleCount,
                  &UsbIoHandleBuffer
                  );
  if (EFI_ERROR (Status)) {
    return FALSE;
  }
  for (Index = 0; Index < UsbIoHandleCount; Index++) {
    //
    // Get the Usb IO interface.
    //
    Status = gBS->HandleProtocol(
                    UsbIoHandleBuffer[Index],
                    &gEfiUsbIoProtocolGuid,
                    (VOID **) &UsbIo
                    );
    if (EFI_ERROR (Status)) {
      continue;
    }

    UsbIoDevicePath = DevicePathFromHandle (UsbIoHandleBuffer[Index]);

    if (UsbIoDevicePath == NULL || CompareMem (UsbIoDevicePath, DevicePath, GetDevicePathSize(UsbIoDevicePath))) {
      continue;
    }
    //
    // Check Vendor Id and Product Id.
    //
    Status = UsbIo->UsbGetDeviceDescriptor (UsbIo, &DevDesc);
    if (EFI_ERROR (Status)) {
      continue;
    }

    DeviceClass    = DevDesc.DeviceClass;
    DeviceSubClass = DevDesc.DeviceSubClass;
    if (DeviceClass == 0) {
      //
      // If Class in Device Descriptor is set to 0, use the Class, SubClass and
      // Protocol in Interface Descriptor instead.
      //
      Status = UsbIo->UsbGetInterfaceDescriptor (UsbIo, &IfDesc);
      if (EFI_ERROR (Status)) {
        continue;
      }
      DeviceClass    = IfDesc.InterfaceClass;
      DeviceSubClass = IfDesc.InterfaceSubClass;
    }
    // Find USB CDROM Device
    if ((DeviceClass == USB_MASS_STORE_CLASS) && (DeviceSubClass == USB_MASS_STORE_8020I)) {
         return TRUE;
    }
  }

  return FALSE;
}

/**
  Brief description of GetBootDeviceTypeFromDevicePath.

  @param  DevicePath

**/
UINT16
EFIAPI
GetBootDeviceTypeFromDevicePath (
  IN  EFI_DEVICE_PATH_PROTOCOL     *DevicePath
)
{
  ACPI_HID_DEVICE_PATH          *Acpi;
  EFI_DEVICE_PATH_PROTOCOL      *TempDevicePath;
  EFI_DEVICE_PATH_PROTOCOL      *LastDeviceNode;
  UINT16                        BootType;
  UINTN                         NodeLen;
  UINTN                         PathLen;
  EFI_STATUS                    Status;
  EFI_HANDLE                    Handle;
  EFI_BLOCK_IO_PROTOCOL         *BlkIo;

  if (NULL == DevicePath) {
    return BDS_EFI_UNSUPPORT;
  }

  NodeLen = 0;
  PathLen = 0;
  TempDevicePath = DevicePath;

  while (!IsDevicePathEndType (TempDevicePath)) {
    NodeLen = DevicePathNodeLength(TempDevicePath);
    PathLen += NodeLen;
    //*DevicePathLen = PathLen;

    switch (DevicePathType (TempDevicePath)) {
    case BBS_DEVICE_PATH:
      return BDS_LEGACY_BBS_BOOT;
    case MEDIA_DEVICE_PATH:

      if (DevicePathSubType (TempDevicePath) == MEDIA_HARDDRIVE_DP) {
        return BDS_EFI_MEDIA_HD_BOOT;

      } else if (DevicePathSubType (TempDevicePath) == MEDIA_CDROM_DP) {
        return BDS_EFI_MEDIA_CDROM_BOOT;
//add-klk-lyang-P000A-start//
      } else if (DevicePathSubType (TempDevicePath) == MEDIA_PIWG_FW_FILE_DP) {
        return BDS_EFI_MEDIA_FW_FILE_BOOT;
      }
//add-klk-lyang-P000A-end//
      break;
    case ACPI_DEVICE_PATH:
      Acpi = (ACPI_HID_DEVICE_PATH *) TempDevicePath;

      if (EISA_ID_TO_NUM (Acpi->HID) == 0x0604) {
        return BDS_EFI_ACPI_FLOPPY_BOOT;
      }

      break;
    case MESSAGING_DEVICE_PATH:
      //
      // Get the last device path node
      //
      LastDeviceNode = NextDevicePathNode (TempDevicePath);

      if (DevicePathSubType(LastDeviceNode) == MSG_DEVICE_LOGICAL_UNIT_DP) {
        //
        // if the next node type is Device Logical Unit, which specify the Logical Unit Number (LUN),
        // skip it
        //
        LastDeviceNode = NextDevicePathNode (LastDeviceNode);
      }

      //
      // if the device path not only point to driver device, it is not a messaging device path,
      //
      if (!IsDevicePathEndType (LastDeviceNode)) {
        break;
      }

      switch (DevicePathSubType (TempDevicePath)) {
      case MSG_ATAPI_DP:
        BootType = BDS_EFI_MESSAGE_ATAPI_BOOT;
        break;
      case MSG_USB_DP:
        if (IsUsbCdromBootOption(DevicePath)) {
          BootType = BDS_EFI_MESSAGE_USB_CDROM_BOOT;//NONSTANDARD
        } else {
          BootType = BDS_EFI_MESSAGE_USB_DEVICE_BOOT;
        }
        break;
      case MSG_SCSI_DP:
        BootType = BDS_EFI_MESSAGE_SCSI_BOOT;
        break;
      case MSG_SATA_DP:
//add-klk-lyang-P000A-start//  hd cd-rom
        Status = gBS->LocateDevicePath (&gEfiBlockIoProtocolGuid, &DevicePath, &Handle);
        if (!EFI_ERROR(Status)) {
           Status = gBS->HandleProtocol (
                 Handle,
                 &gEfiBlockIoProtocolGuid,
                 (VOID **) &BlkIo
                 );
          if (BlkIo->Media->RemovableMedia) {
             BootType = BDS_EFI_MEDIA_CDROM_BOOT;
          } else {
             BootType = BDS_EFI_MESSAGE_SATA_BOOT;
          }
        } else {
           BootType = BDS_EFI_MESSAGE_SATA_BOOT;
        }
//add-klk-lyang-P000A-end//
        break;
      case MSG_NVME_NAMESPACE_DP:
        BootType = BDS_EFI_MESSAGE_NVME_BOOT;
        break;
      case MSG_MAC_ADDR_DP:
      case MSG_VLAN_DP:
      case MSG_IPv4_DP:
      case MSG_IPv6_DP:
        BootType = BDS_EFI_MESSAGE_MAC_BOOT;
        break;
      default:
        BootType = BDS_EFI_MESSAGE_MISC_BOOT;
        break;
      }

      return BootType;
    default:
      break;
    }

    TempDevicePath = NextDevicePathNode (TempDevicePath);
  }

  return BDS_EFI_UNSUPPORT;
}

